﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Emgu.CV;
using Emgu.CV.Structure;
using System.Drawing;
using System.IO;
using SlimDX.Multimedia;
using SlimDX.XAudio2;
using SlimDX;

namespace SoundPlugin
{
    public class ShapeSoundProcessor
    {
        #region Properties
        private bool detecting;
        private ReaderWriterLockSlim detectLock = new ReaderWriterLockSlim();
        public bool Detect 
        {
            get 
            {
                try
                {
                    detectLock.EnterReadLock();
                    return this.detecting;
                }
                finally 
                {
                    detectLock.ExitReadLock();
                }
            }
            set 
            {
                try
                {
                    detectLock.EnterWriteLock();
                    this.detecting = value;
                }
                finally 
                {
                    detectLock.ExitWriteLock();
                }
            }
        }

        private bool playing = true;
        private ReaderWriterLockSlim playLock = new ReaderWriterLockSlim();
        public bool Playing
        {
            get
            {
                try
                {
                    playLock.EnterReadLock();
                    return this.playing;
                }
                finally
                {
                    playLock.ExitReadLock();
                }
            }
            set
            {
                try
                {
                    playLock.EnterWriteLock();
                    this.playing = value;
                }
                finally
                {
                    playLock.ExitWriteLock();
                }
            }
        }

        private MemStorage memory;
        private XAudio2 soundDevice;
        private MasteringVoice mVoice;
        private SoundPlugin plugin;
        private Random random;
        #endregion

        #region Constructors
        public ShapeSoundProcessor(SoundPlugin plugin) 
        {
            this.plugin = plugin;
            this.Detect = false;
        }
        #endregion

        #region Public Methods
        public void RunDetect() 
        {
            Thread detect = new Thread(new ThreadStart(this.ShapeDetectionThread));
            detect.Name = "Run detect";
            detect.Start();
            detect.Join();

        }

        public void GetAllSounds()
        {
            String path = AppDomain.CurrentDomain.BaseDirectory;
            path = path.Substring(0, path.IndexOf("bin"));
            path = path + "plugins\\sounds";
            DirectoryInfo soundsDirectory = new DirectoryInfo(path);
            this.plugin.ClearSounds();
            //get all wav files from Sounds directory
            foreach (FileInfo sound in soundsDirectory.GetFiles())
            {
                if (sound.Extension == ".wav")
                {
                    this.plugin.AddSound(sound);
                }
            }
        }

        public void AssignSoundToShape(ref List<Shape> shapes, int index, FileInfo sound)
        {
            shapes[index].SoundFile = sound;
        }

        public void PlaySound(Shape shape, bool waitForEndOfFile, bool assigningSound)
        {
            String fileName = shape.SoundFile.FullName;
            if (this.soundDevice == null)
            {
                this.soundDevice = new XAudio2();
                this.mVoice = new MasteringVoice(this.soundDevice);
            }

            WaveStream stream = new WaveStream(fileName);
            AudioBuffer buffer = new AudioBuffer();
            buffer.AudioData = stream;
            buffer.AudioBytes = (int)stream.Length;
            buffer.Flags = BufferFlags.EndOfStream;

            Thread PlaySoundThread = new Thread(
                delegate()
                {
                    SourceVoice voice = new SourceVoice(soundDevice, stream.Format);
                    voice.SubmitSourceBuffer(buffer);
                    voice.Start();

                    while (voice.State.BuffersQueued > 0 && shape.Process && !shape.IsPaused)
                    {
                        Thread.Sleep(5);
                    }

                    try
                    {
                        if (voice.State.BuffersQueued > 0)
                        {
                            voice.Stop();
                        }
                    }
                    catch (Exception ex)
                    {

                    }
                    buffer.Dispose();
                    voice.Dispose();
                    stream.Dispose();

                    if (!waitForEndOfFile && !assigningSound)
                    {
                        shape.IsActive = false;
                    }
                });

            PlaySoundThread.Priority = ThreadPriority.AboveNormal;
            PlaySoundThread.Start();
            
            if (waitForEndOfFile)
            {
                PlaySoundThread.Join();
            }
        }


        #endregion

        #region Private Methods

        private void ShapeDetectionThread() 
        {
            this.Detect = true;
            bool done = false;
            int[] nrOfShapes = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            int i = 0;
            DateTime startTime = DateTime.Now;
            TimeSpan elapsed = startTime - DateTime.Now;
            while((!done) && (elapsed.Milliseconds / 1000 < 20) && this.Detect ) 
            {
                if (i == 10) 
                {
                    i = 0;
                }

                Image<Bgr, byte> img = new Image<Bgr,byte>((Bitmap)this.plugin.Host.GetWebcamImage());
                Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();
                gray = gray.ThresholdToZero(new Gray(80));

                Gray cannyThreshold = new Gray(100);
                Gray cannyThresholdLinking = new Gray(20);
                
                Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking);
                Contour<Point> contours = cannyEdges.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_TC89_KCOS , Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_TREE);
                nrOfShapes[i] = this.CountShapes(contours);
                if (nrOfShapes[i] > 0)
                {
                    if ((double)nrOfShapes.Sum() / nrOfShapes.Length / nrOfShapes.Max() > 0.9)
                    {
                        DetectShapes(contours);
                        this.plugin.BuildShapeImage(img.CopyBlank().Bitmap);
                        done = true;
                    }
                }
                i++;
                elapsed = DateTime.Now - startTime;
                Thread.Sleep(50);
            }
            this.Detect = false;
        }

        private int CountShapes(Contour<Point> allContours)
        {
            int nr = 0;
            using (MemStorage storage = new MemStorage())
            for (Contour<Point> contours = allContours ; contours != null; contours = contours.HNext)
            {
                Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.001, storage);
                if (currentContour.Area > 1000)
                {
                    nr++;
                }
            }
            return nr;
        }

        private void DetectShapes(Contour<Point> allContours)
        {
            if (this.random == null) 
            {
                this.random = new Random();
            }
            this.plugin.ClearShapes();

            this.memory = new MemStorage();
            for (Contour<Point> contours = allContours; contours != null; contours = contours.HNext)
            {
                Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.001, this.memory);
                if (currentContour.Area > 1000)
                {
                    Int32 r = random.Next(255);
                    Int32 b = random.Next(255);
                    Int32 g = random.Next(255);
                    this.plugin.AddShape(new Shape(currentContour , Color.FromArgb(r, g, b)));
                }
            }
        }
    #endregion

    }
}
